package website

import (
	"container/list"
	"crypto/rand"
	"encoding/hex"
	"net"
	"net/http"
	"net/http/fcgi"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"
)

func GetPID() string {
	return ToStr(os.Getpid())
}

func RunServer(cfg *Config, mux http.Handler) {
	go func() {
		for {
			err := WritePidFile(cfg.PidPath, GetPID())
			if nil != err {
				logFatal(err)
				break
			}
			time.Sleep(time.Duration(1 * time.Second))
		}
	}()

	// if cfg.Daemon {
	// 	ret, err := Daemon(0, 0)
	// 	if nil != err && ret == -1 {
	// 		logFatal(err)
	// 		return
	// 	}
	// }

	runMsgArr := make([]string, 0, 0)
	runMsgArr = append(runMsgArr, "==>")
	runMsgArr = append(runMsgArr, "Web server running.")
	runMsgArr = append(runMsgArr, "PID:"+GetPID()+",")
	runMsgArr = append(runMsgArr, "Addr:"+cfg.Addr+".")

	if cfg.EnableFcgi {
		runMsgArr = append(runMsgArr, "Fcgi:"+ToStr(cfg.EnableFcgi)+".")
	}

	if cfg.EnableTLS {
		runMsgArr = append(runMsgArr, "SSL:"+ToStr(cfg.EnableTLS)+",")
		runMsgArr = append(runMsgArr, "TLS_Addr:"+ToStr(cfg.TlsAddr)+",")
		runMsgArr = append(runMsgArr, "TLS_Url:"+ToStr(cfg.TlsUrl)+".")
	}

	runMsg := strings.Join(runMsgArr, " ")

	logInfo(runMsg)

	if cfg.EnableFcgi {
		listener, err := net.Listen("tcp", cfg.Addr)
		if err != nil {
			logFatal(err.Error())
			return
		}
		err = fcgi.Serve(listener, mux)
		if nil != err {
			logFatal(err)
			return
		}
	} else {
		if cfg.EnableTLS {
			go func() {
				err := http.ListenAndServe(cfg.Addr, http.RedirectHandler(cfg.TlsUrl, http.StatusMovedPermanently))
				if nil != err {
					logFatal(err)
					return
				}
			}()

			err := http.ListenAndServeTLS(cfg.TlsAddr, cfg.TlsCert, cfg.TlsKey, mux)
			if nil != err {
				logFatal(err)
				return
			}
		} else {
			err := http.ListenAndServe(cfg.Addr, mux)
			if nil != err {
				logFatal(err)
				return
			}
		}
	}
}

var (
	SessionName    = "SID"
	SessionManager = &Manager{list: list.New(), timeOut: 30, sessions: make(map[string]*list.Element)}
)

type Session struct {
	lock    sync.RWMutex
	sid     string
	regTime time.Time
	value   map[interface{}]interface{}
}

type Manager struct {
	lock     sync.RWMutex
	list     *list.List
	timeOut  int
	sessions map[string]*list.Element
}

func (s *Session) Set(key, value interface{}) {
	s.lock.Lock()
	defer s.lock.Unlock()
	s.value[key] = value
}

func (s *Session) Get(key interface{}) interface{} {
	s.lock.RLock()
	defer s.lock.RUnlock()
	if v, ok := s.value[key]; ok {
		return v
	} else {
		return nil
	}
}

func (s *Session) Del(key interface{}) {
	s.lock.Lock()
	defer s.lock.Unlock()
	if _, ok := s.value[key]; ok {
		delete(s.value, key)
	}
}

func (s *Session) ID() string {
	return s.sid
}

func (m *Manager) Init(sid string, timeOut int) *Session {
	m.timeOut = timeOut
	m.lock.RLock()
	if sess, ok := m.sessions[sid]; ok {
		go m.Update(sid)
		m.lock.RUnlock()
		return sess.Value.(*Session)
	} else {
		m.lock.RUnlock()
		m.lock.Lock()
		nsess := &Session{sid: sid, regTime: time.Now(), value: make(map[interface{}]interface{})}
		sess := m.list.PushBack(nsess)
		m.sessions[sid] = sess
		m.lock.Unlock()
		return nsess
	}
	return nil
}

func (m *Manager) Update(sid string) {
	m.lock.RLock()
	defer m.lock.RUnlock()
	if sess, ok := m.sessions[sid]; ok {
		sess.Value.(*Session).regTime = time.Now()
		m.list.MoveToFront(sess)
	}
}

func (m *Manager) Destroy(sid string) {
	m.lock.Lock()
	defer m.lock.Unlock()
	if sess, ok := m.sessions[sid]; ok {
		delete(m.sessions, sid)
		m.list.Remove(sess)
	}
}

func (m *Manager) GC() {
	m.lock.RLock()
	for {
		sess := m.list.Back()
		if sess == nil {
			break
		}
		cTime := sess.Value.(*Session).regTime.Add(+time.Second * time.Duration(m.timeOut))
		if cTime.Before(time.Now()) {
			m.lock.RUnlock()
			m.lock.Lock()
			m.list.Remove(sess)
			delete(m.sessions, sess.Value.(*Session).sid)
			m.lock.Unlock()
			m.lock.RLock()
		} else {
			break
		}
	}
	m.lock.RUnlock()
	time.AfterFunc(time.Duration(m.timeOut)*time.Second, func() { SessionManager.GC() })
}

func UUID() (string, error) {
	uuid := make([]byte, 16)
	n, err := rand.Read(uuid)
	if n != len(uuid) || err != nil {
		return "", err
	}
	uuid[8] = 0x80
	uuid[4] = 0x40
	return hex.EncodeToString(uuid), nil
}

func RegSession(w http.ResponseWriter, r *http.Request, timeout int, enableTls bool, manager *Manager) *Session {
	var session *Session
	cookie, err := r.Cookie(SessionName)
	if nil != err && err == http.ErrNoCookie {
		sid, _ := UUID()
		session = manager.Init(sid, timeout)
		cookie = &http.Cookie{
			Name:     SessionName,
			Value:    url.QueryEscape(sid),
			Path:     "/",
			HttpOnly: true,
		}
	} else {
		sid, _ := url.QueryUnescape(cookie.Value)
		session = manager.Init(sid, timeout)
	}
	if enableTls {
		cookie.Secure = true
	}
	cookie.MaxAge = 0
	cookie.Expires = time.Now().Add(+time.Duration(timeout) * time.Second)
	http.SetCookie(w, cookie)
	r.AddCookie(cookie)
	go manager.GC()
	return session
}
